﻿using System;
using System.Collections.Generic;
using System.Text;

namespace JSONUtil
{
    public class JSONSerializer
    {
        public static IJSONObject Deserialize(string jsonText)
        {
            return ReadObject(new JSONTextReader(jsonText)) as IJSONObject;
        }

        public static string Serialize(IJSONObject jsonObject)
        {
            return SerializeObject(jsonObject);
        }

        static string Serialize(JSONArray jsonArray)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append('[');
            bool isFirst = true;
            foreach (var item in jsonArray.Items)
            {
                if (!isFirst)
                    sb.Append(", ");
                isFirst = false;

                sb.Append(SerializeObject(item));
            }
            sb.Append(']');
            return sb.ToString();
        }

        static string Serialize(JSONObject jsonObject)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append('{');
            bool isFirst = true;
            foreach (var member in jsonObject.Members)
            {
                if (!isFirst)
                    sb.Append(", ");
                isFirst = false;

                sb.Append('"');
                sb.Append(member.Key);
                sb.Append('"');

                sb.Append(':');

                sb.Append(SerializeObject(member.Value));
            }
            sb.Append('}');
            return sb.ToString();
        }

        static string SerializeObject(object obj)
        {
            StringBuilder sb = new StringBuilder();
            if (obj == null)
            {
                sb.Append("null");
            }
            else if (obj is bool)
            {
                sb.Append(obj.ToString().ToLower());
            }
            else if (obj is string || obj is DateTime)
            {
                AppendWrappedWithDoubleQuotes(obj.ToString(), sb);
            }
            else if (obj is JSONObject)
            {
                sb.Append(Serialize((JSONObject)(obj)));
            }
            else if (obj is JSONArray)
            {
                sb.Append('[');
                bool isFirst = true;
                foreach (var item in ((JSONArray)obj).Items)
                {
                    if (!isFirst)
                        sb.Append(", ");
                    isFirst = false;

                    sb.Append(SerializeObject(item));
                }
                sb.Append(']');
            }
            else
                sb.Append(obj);

            return sb.ToString();
        }

        static object ReadObject(JSONTextReader jsonreader)
        {
            StringBuilder primitiveValue = new StringBuilder();

            jsonreader.SkipWhiteSpaces();

            char ch = jsonreader.Read();
            if (ch == '{')
            {
                JSONObject jsonObject = new JSONObject();
                while (true)
                {
                    jsonreader.SkipWhiteSpaces();
                    ch = jsonreader.Read();
                    if (ch == '}')
                    {
                        return jsonObject;
                    }
                    else if (ch != ',')
                    {
                        jsonreader.Position--;
                        KeyValuePair<string, object> member = ReadMember(jsonreader);
                        jsonObject.Members.Add(member);
                    }
                }
            }
            else if (ch == '[')
            {
                JSONArray jsonArray = new JSONArray();
                while (true)
                {
                    jsonreader.SkipWhiteSpaces();
                    ch = jsonreader.Read();
                    if (ch == ']')
                    {
                        return jsonArray;
                    }
                    else if (ch != ',')
                    {
                        jsonreader.Position--;
                        object item = ReadObject(jsonreader);
                        jsonArray.Items.Add(item);
                    }
                }
            }
            else
            {
                jsonreader.Position--;
                return ReadPrimitive(jsonreader);
            }

            return null;
        }

        static object ReadPrimitive(JSONTextReader jsonreader)
        {
            StringBuilder primitiveValue = new StringBuilder();
            while (true)
            {
                char ch = jsonreader.Read();
                if (ch == '"')
                {
                    string str = jsonreader.ReadUntil('"');
                    return str;
                }
                else if (ch == ',' || ch == ']' || ch == '}')
                {
                    jsonreader.Position--;
                    string value = primitiveValue.ToString();
                    if (String.Equals(value, "null"))
                        return null;
                    if (String.Equals(value, "true"))
                        return true;
                    if (String.Equals(value, "false"))
                        return false;

                    int i;
                    if (int.TryParse(value, out i))
                        return i;
                    long l;
                    if (long.TryParse(value, out l))
                        return l;
                    float f;
                    if (float.TryParse(value, out f))
                        return f;
                    double d;
                    if (double.TryParse(value, out d))
                        return d;
                    DateTime dt;
                    if (DateTime.TryParse(value, out dt))
                        return dt;

                    //return if nothing matches... then return as string;
                    return value;
                }
                else
                {
                    primitiveValue.Append(ch);
                }
            }
        }

        static KeyValuePair<string, object> ReadMember(JSONTextReader jsonReader)
        {
            int start = jsonReader.Position;
            if (jsonReader.SkipTill('"') == false)
                ThrowUnexpectedTokenException(start, jsonReader, '"');

            string memberName = jsonReader.ReadUntil('"');

            start = jsonReader.Position;
            if (jsonReader.SkipTill(':') == false)
                ThrowUnexpectedTokenException(start, jsonReader, ':');

            object memberValue = ReadObject(jsonReader);

            return new KeyValuePair<string, object>(memberName, memberValue);
        }

        static void AppendWrappedWithDoubleQuotes(string val, StringBuilder sb)
        {
            sb.Append('"');
            sb.Append(val);
            sb.Append('"');
        }

        static void ThrowUnexpectedTokenException(int startPosition, JSONTextReader jsonReader, char expectedChar)
        {
            throw new UnexpectedTokenException(expectedChar, startPosition, jsonReader.Position, jsonReader.Text);
        }
    }

    public class UnexpectedTokenException : ApplicationException
    {
        public string OriginalJSONText;
        public UnexpectedTokenException(char expectedToken, int rangeStart, int rangeEnd, string text)
            : base(String.Format("Unexpected Token in range {0}-{1}. Expected '{2}'.", rangeStart, rangeEnd, expectedToken))
        {
            OriginalJSONText = text;
        }
    }
}
